/**
 * LY.com Inc.
 * Copyright (c) 2004-2019 All Rights Reserved.
 */
package com.gitee.kamismile.stoneComEx.util.converter.beancopier;

import com.gitee.kamismile.stone.commmon.exception.BusinessException;
import org.apache.commons.beanutils.ConversionException;
import org.springframework.cglib.core.Converter;

import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author dong.li
 * @version $Id: DateConverterBeanCopier, v 0.1 2019/3/22 16:09 dong.li Exp $
 */
public class DateConverterBeanCopier implements Converter {

    private String[] patterns;

    private TimeZone timeZone;

    private Locale locale;

    private boolean useLocaleFormat;


    public boolean isUseLocaleFormat() {
        return useLocaleFormat;
    }

    public void setUseLocaleFormat(boolean useLocaleFormat) {
        this.useLocaleFormat = useLocaleFormat;
    }

    public Locale getLocale() {
        return locale;
    }

    public void setLocale(Locale locale) {
        this.locale = locale;
    }

    public TimeZone getTimeZone() {
        return timeZone;
    }

    public void setTimeZone(TimeZone timeZone) {
        this.timeZone = timeZone;
    }

    public String[] getPatterns() {
        return patterns;
    }

    public void setPatterns(final String[] patterns) {
        this.patterns = patterns;
        if (patterns != null && patterns.length > 1) {
            final StringBuilder buffer = new StringBuilder();
            for (int i = 0; i < patterns.length; i++) {
                if (i > 0) {
                    buffer.append(", ");
                }
                buffer.append(patterns[i]);
            }
        }
        setUseLocaleFormat(true);
    }


    @Override
    public Object convert(Object value, Class targetType, Object o1) {
        if (Objects.isNull(value)) {
            return null;
        }

        if (!targetType.equals(Date.class) && !targetType.equals(Timestamp.class) && !targetType.equals(Calendar.class)) {
            return value;
        }

        final Class<?> sourceType = value.getClass();

        // Handle java.sql.Timestamp
        if (value instanceof java.sql.Timestamp) {

            // ---------------------- JDK 1.3 Fix ----------------------
            // N.B. Prior to JDK 1.4 the Timestamp's getTime() method
            //      didn't include the milliseconds. The following code
            //      ensures it works consistently accross JDK versions
            final java.sql.Timestamp timestamp = (java.sql.Timestamp) value;
            long timeInMillis = ((timestamp.getTime() / 1000) * 1000);
            timeInMillis += timestamp.getNanos() / 1000000;
            // ---------------------- JDK 1.3 Fix ----------------------
            return toDate(targetType, timeInMillis);
        }

        // Handle Date (includes java.sql.Date & java.sql.Time)
        if (value instanceof Date) {
            final Date date = (Date) value;
            return toDate(targetType, date.getTime());
        }

        // Handle Calendar
        if (value instanceof Calendar) {
            final Calendar calendar = (Calendar) value;
            return toDate(targetType, calendar.getTime().getTime());
        }

        // Handle Long
        if (value instanceof Long) {
            final Long longObj = (Long) value;
            return toDate(targetType, longObj.longValue());
        }

        // Convert all other types to String & handle
        final String stringValue = value.toString().trim();
        if (stringValue.length() == 0) {
            return value;
        }
        try {
            // Parse the Date/Time
            if (useLocaleFormat) {
                Calendar calendar = null;
                if (patterns != null && patterns.length > 0) {
                    calendar = parse(sourceType, targetType, stringValue);
                } else {
                    final DateFormat format = getFormat(locale, timeZone);
                    calendar = parse(sourceType, targetType, stringValue, format);
                }
                if (Calendar.class.isAssignableFrom(targetType)) {
                    return targetType.cast(calendar);
                } else {
                    return toDate(targetType, calendar.getTime().getTime());
                }
            }
        } catch (Exception e) {
            throw new BusinessException("date format error");
        }
        return value;
    }

    private <T> T toDate(final Class<T> type, final long value) {

        // java.util.Date
        if (type.equals(Date.class)) {
            return type.cast(new Date(value));
        }

        // java.sql.Date
        if (type.equals(java.sql.Date.class)) {
            return type.cast(new java.sql.Date(value));
        }

        // java.sql.Time
        if (type.equals(java.sql.Time.class)) {
            return type.cast(new java.sql.Time(value));
        }

        // java.sql.Timestamp
        if (type.equals(java.sql.Timestamp.class)) {
            return type.cast(new java.sql.Timestamp(value));
        }

        // java.util.Calendar
        if (type.equals(Calendar.class)) {
            Calendar calendar = null;
            if (locale == null && timeZone == null) {
                calendar = Calendar.getInstance();
            } else if (locale == null) {
                calendar = Calendar.getInstance(timeZone);
            } else if (timeZone == null) {
                calendar = Calendar.getInstance(locale);
            } else {
                calendar = Calendar.getInstance(timeZone, locale);
            }
            calendar.setTime(new Date(value));
            calendar.setLenient(false);
            return type.cast(calendar);
        }
        throw new BusinessException("date format error");
    }

    private DateFormat getFormat(final String pattern) {
        final DateFormat format = new SimpleDateFormat(pattern);
        if (timeZone != null) {
            format.setTimeZone(timeZone);
        }
        return format;
    }

    private Calendar parse(final Class<?> sourceType, final Class<?> targetType, final String value) throws Exception {
        Exception firstEx = null;
        for (String pattern : patterns) {
            try {
                final DateFormat format = getFormat(pattern);
                final Calendar calendar = parse(sourceType, targetType, value, format);
                return calendar;
            } catch (final Exception ex) {
                if (firstEx == null) {
                    firstEx = ex;
                }
            }
        }
        if (patterns.length > 1) {
            throw new BusinessException("Error converting '" + toString(sourceType) + "' to '" + toString(targetType));
        } else {
            throw firstEx;
        }
    }

    private Calendar parse(final Class<?> sourceType, final Class<?> targetType, final String value, final DateFormat format) {
        format.setLenient(false);
        final ParsePosition pos = new ParsePosition(0);
        final Date parsedDate = format.parse(value, pos); // ignore the result (use the Calendar)
        if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedDate == null) {
            String msg = "Error converting '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
            if (format instanceof SimpleDateFormat) {
                msg += " using pattern '" + ((SimpleDateFormat) format).toPattern() + "'";
            }
            throw new BusinessException(msg);
        }
        final Calendar calendar = format.getCalendar();
        return calendar;
    }

    String toString(final Class<?> type) {
        String typeName = null;
        if (type == null) {
            typeName = "null";
        } else if (type.isArray()) {
            Class<?> elementType = type.getComponentType();
            int count = 1;
            while (elementType.isArray()) {
                elementType = elementType.getComponentType();
                count++;
            }
            typeName = elementType.getName();
            for (int i = 0; i < count; i++) {
                typeName += "[]";
            }
        } else {
            typeName = type.getName();
        }
        if (typeName.startsWith("java.lang.") ||
                typeName.startsWith("java.util.") ||
                typeName.startsWith("java.math.")) {
            typeName = typeName.substring("java.lang.".length());
        }
        return typeName;
    }

    protected DateFormat getFormat(final Locale locale, final TimeZone timeZone) {
        DateFormat format = null;
        if (locale == null) {
            format = DateFormat.getDateInstance(DateFormat.SHORT);
        } else {
            format = DateFormat.getDateInstance(DateFormat.SHORT, locale);
        }
        if (timeZone != null) {
            format.setTimeZone(timeZone);
        }
        return format;
    }
}
